package de.dhbw.karlsruhe.picsim.rs232;

import de.dhbw.karlsruhe.picsim.model.cpu.ReadOnlyFile;
import de.dhbw.karlsruhe.picsim.model.io.External;
import de.dhbw.karlsruhe.picsim.model.io.ExternalReadOnly;
import de.dhbw.karlsruhe.picsim.model.memory.RegisterValue;
import de.dhbw.karlsruhe.picsim.util.impl.BitUtilities;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NonNls;

import java.util.regex.Pattern;

/**
 * Created by IntelliJ IDEA.
 * User: goesen
 * Date: May 5, 2010
 * Time: 11:28:27 AM
 */

/**
 * schreibt die Werte von tris und co nur wenn sie sich nach einem Circle geändert haben.
 */
public final class ProtocolLogicImpl implements ProtocolLogic {
    private static final Log log = LogFactory.getLog(ProtocolLogicImpl.class);

    private final ReadOnlyFile portA;
    private final ReadOnlyFile portB;
    private final ReadOnlyFile trisA;
    private final ReadOnlyFile trisB;
    private final External externalPins;
    private final ComHandler comHandler;
    private int count;
    private RegisterValue portAData;
    private RegisterValue portBData;
    private RegisterValue trisAData;
    private RegisterValue trisBData;
    private static final int MAX_HOLD_BACK_MESSAGES = 20;
    private static final Pattern LINUX_NEWLINE = Pattern.compile("\n");

    public ProtocolLogicImpl(ReadOnlyFile portA, ReadOnlyFile portB, ReadOnlyFile trisA, ReadOnlyFile trisB,
                             ComHandler comHandler, External externalPins) {
        this.portA = portA;
        this.portB = portB;
        this.trisA = trisA;
        this.trisB = trisB;
        this.externalPins = externalPins;
        this.comHandler = comHandler;
        initData();
        count = 0;
    }

    private void initData() {
        portAData = portA.getData();
        portBData = portB.getData();
        trisAData = trisA.getData();
        trisBData = trisB.getData();
    }

    @Override
    public void sendMessageIfNecessary() {
        count++;
        if (hasChanged() || count >= MAX_HOLD_BACK_MESSAGES) {
            count = 0;
            initData();
            if (comHandler.isReady()) {
                sendMessageAndWait();

            }
        }

    }

    @Override
    public void sendMessageAndWait() {
        if (comHandler.isReady()) {
            final PicViewProtocol viewProtocol = new PicViewProtocolImpl();
            final String message = viewProtocol.getMessage(BitUtilities.subBits(trisAData, 0, 5),
                                                           BitUtilities.subBits(portAData, 0, 5), trisBData, portBData);
            final MyListener listener = new MyListener(viewProtocol);
            comHandler.addListener(listener);
            synchronized (listener) {
                /* try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    log.error(e);
                }*/
                comHandler.sendString(message);
                if (!listener.isFinished()) {
                    try {
                        log.debug("waiting for " + listener);
                        listener.wait();
                        log.debug("listener" + listener + " finished");
                    } catch (InterruptedException e) {
                        log.error(e);
                    }
                } else {
                    log.debug("fast listener");
                }
                //jetzt müssen wir unsere Daten neupollen, sonst denken wir nachher das es eine Änderung gegeben hat
                //obwohl wir das waren
                initData();


            }
        }
    }

    @SuppressWarnings({"OverlyComplexBooleanExpression", "MethodWithMoreThanThreeNegations"})
    private boolean hasChanged() {
        final RegisterValue newValue = portA.getData();
        final boolean portAChanged = !portAData.equals(newValue);
        if (portAChanged) {
            log.debug("portA Changed");
        }
        return portAChanged || !portBData.equals(portB.getData())
               || !trisA.getData().equals(trisAData) || !trisBData.equals(trisB.getData());
    }

    private class MyListener implements ComHandler.Listener {
        private final PicViewProtocol viewProtocol;
        private boolean finished = false;

        private MyListener(PicViewProtocol viewProtocol) {
            this.viewProtocol = viewProtocol;
        }

        @Override
        public void processMessage(@NonNls final String message) {
            final String strippedMessage = LINUX_NEWLINE.matcher(message).replaceAll("");

            final ExternalReadOnly theirPorts = viewProtocol.getTheirPorts(strippedMessage);

            if (theirPorts != null) {
                externalPins.setPortA(theirPorts.getPortA());
                externalPins.setPortB(theirPorts.getPortB());
            } else {
                log.error("illegal message " + strippedMessage + " received");
            }
            synchronized (this) {
                finished = true;
                notify();
            }
        }

        public boolean isFinished() {
            return finished;
        }
    }
}
